﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="DragHandler.cs" company="">
//   
// </copyright>
// <summary>
//   The drag handler.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace Libium.Controls
{
    using System;
    using System.Collections.Generic;
    using System.Windows.Forms;

    using Libium.Core;
    using Libium.WindowsForms.Controls;

    /// <summary>
    /// The drag handler.
    /// </summary>
    public class DragHandler : Disposable
    {
        #region Fields

        /// <summary>
        /// The _controls.
        /// </summary>
        private readonly List<Control> _controls = new List<Control>();

        /// <summary>
        /// The _mouse down button.
        /// </summary>
        private MouseButtons _mouseDownButton = MouseButtons.None;

        /// <summary>
        /// The _mouse down x.
        /// </summary>
        private int _mouseDownX;

        /// <summary>
        /// The _mouse down y.
        /// </summary>
        private int _mouseDownY;

        #endregion

        #region Constructors and Destructors

        /// <summary>
        /// Initializes static members of the <see cref="DragHandler"/> class.
        /// </summary>
        static DragHandler()
        {
            OffsetToStartDrag = 5;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="DragHandler"/> class.
        /// </summary>
        /// <param name="source">
        /// The source.
        /// </param>
        public DragHandler(ICopySource source = null)
        {
            Source = source;
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// Gets or sets the offset to start drag.
        /// </summary>
        public static int OffsetToStartDrag { get; set; }

        /// <summary>
        /// Gets or sets the source.
        /// </summary>
        public ICopySource Source { get; set; }

        #endregion

        #region Public Methods and Operators

        /// <summary>
        /// The add control.
        /// </summary>
        /// <param name="control">
        /// The control.
        /// </param>
        public void AddControl(Control control)
        {
            control.MouseDown += OnMouseDown;
            control.MouseUp += OnMouseUp;
            control.MouseMove += OnMouseMove;
            control.QueryContinueDrag += OnQueryContinueDrag;
            control.GiveFeedback += OnGiveFeedback;
            _controls.Add(control);
        }

        #endregion

        #region Methods

        /// <summary>
        /// The do dispose.
        /// </summary>
        protected override void DoDispose()
        {
            foreach (var control in _controls)
            {
                control.MouseDown -= OnMouseDown;
                control.MouseUp -= OnMouseUp;
                control.MouseMove -= OnMouseMove;
                control.MouseLeave -= OnMouseLeave;
                control.QueryContinueDrag -= OnQueryContinueDrag;
                control.GiveFeedback -= OnGiveFeedback;
            }
        }

        /// <summary>
        /// The on give feedback.
        /// </summary>
        /// <param name="sender">
        /// The sender.
        /// </param>
        /// <param name="e">
        /// The e.
        /// </param>
        protected virtual void OnGiveFeedback(object sender, GiveFeedbackEventArgs e)
        {
        }

        /// <summary>
        /// The on query continue drag.
        /// </summary>
        /// <param name="sender">
        /// The sender.
        /// </param>
        /// <param name="e">
        /// The e.
        /// </param>
        protected virtual void OnQueryContinueDrag(object sender, QueryContinueDragEventArgs e)
        {
        }

        /// <summary>
        /// The on start drag.
        /// </summary>
        /// <param name="control">
        /// The control.
        /// </param>
        protected virtual void OnStartDrag(Control control)
        {
            var effects = DragDropEffects.None;

            if (Source == null)
            {
                return;
            }

            var options = Source.GetCopyOptions();

            if (options.CanCopy)
            {
                effects |= DragDropEffects.Copy;
            }

            if (options.CanMove)
            {
                effects |= DragDropEffects.Move;
            }

            if (options.CanLink)
            {
                effects |= DragDropEffects.Link;
            }

            if (effects == DragDropEffects.None)
            {
                return;
            }

            var data = Source.StartCopy();

            if (data == null)
            {
                return;
            }

            var dataObject = new CopyDataObject(data);
            dataObject.SetData("Libium.Source", control);
            effects = control.DoDragDrop(dataObject, effects);

            if (effects == DragDropEffects.None)
            {
                return;
            }

            var action = CopyAction.None;

            if ((effects & DragDropEffects.Copy) != 0)
            {
                action = CopyAction.Copy;
            }
            else if ((effects & DragDropEffects.Move) != 0)
            {
                action = CopyAction.Move;
            }
            else if ((effects & DragDropEffects.Link) != 0)
            {
                action = CopyAction.Link;
            }

            Source.FinishCopy(action);
        }

        /// <summary>
        /// The on mouse down.
        /// </summary>
        /// <param name="sender">
        /// The sender.
        /// </param>
        /// <param name="e">
        /// The e.
        /// </param>
        private void OnMouseDown(object sender, MouseEventArgs e)
        {
            _mouseDownX = e.X;
            _mouseDownY = e.Y;
            _mouseDownButton = e.Button;
        }

        /// <summary>
        /// The on mouse leave.
        /// </summary>
        /// <param name="sender">
        /// The sender.
        /// </param>
        /// <param name="e">
        /// The e.
        /// </param>
        private void OnMouseLeave(object sender, EventArgs e)
        {
            if (_mouseDownButton == MouseButtons.None)
            {
                return;
            }

            _mouseDownButton = MouseButtons.None;
            OnStartDrag((Control)sender);
        }

        /// <summary>
        /// The on mouse move.
        /// </summary>
        /// <param name="sender">
        /// The sender.
        /// </param>
        /// <param name="e">
        /// The e.
        /// </param>
        private void OnMouseMove(object sender, MouseEventArgs e)
        {
            if (_mouseDownButton == MouseButtons.None)
            {
                return;
            }

            if (Math.Abs(_mouseDownX - e.X) <= OffsetToStartDrag && Math.Abs(_mouseDownY - e.Y) <= OffsetToStartDrag)
            {
                return;
            }

            _mouseDownButton = MouseButtons.None;
            OnStartDrag((Control)sender);
        }

        /// <summary>
        /// The on mouse up.
        /// </summary>
        /// <param name="sender">
        /// The sender.
        /// </param>
        /// <param name="e">
        /// The e.
        /// </param>
        private void OnMouseUp(object sender, MouseEventArgs e)
        {
            _mouseDownButton = MouseButtons.None;

        }

        #endregion
    }
}